﻿package hapi.mvc;

#if neko
import neko.Lib;
#elseif php
import php.Lib;
#end
import hapi.mvc.View;

class TemplateView implements View {
	
	var provider:TemplateProvider;
	var templateName:String;
	var context:Dynamic;
	var macros:Dynamic;
	
	public function new(provider, name, ?context, ?macros) {
		this.templateName = name;
		this.provider = provider;
		this.context = context == null ? {} : context;
		this.macros = macros == null ? {} : macros;
		addMacro("sub", renderSubtemplate);
		addMacro("render", renderObject);
		addMacro("renderMany", renderIterable);
		addMacro("dateFormat", dateFormat);
		addMacro("noCache", noCache);
		addMacro("set", setMacro);
		addMacro("condSet", ternarySetMacro);
		addMacro("ternary", ternaryMacro);
		addMacro("valid", markValidationError);
	}
	
	public function display() {
		Lib.print(renderTemplate());
	}
	
	public function assign(name:String, value:Dynamic):Void {
		try{
			Reflect.setField(context, name, value);
		}catch (e:Dynamic) {
			trace("assign error - " + name +" -> <"+value+"> : ");
			trace(e);
		}
	}
	
	public function assignDisplayable(value:Displayable):Void {
		value.displayOn(this);
	}
	
	function renderSubtemplate(resolve:String->Dynamic, name:String) {
		return renderTemplate(name);
	}
	
	public function renderTemplate(?name):String {
		if (name == null) name = templateName;
		var result = null;
		try {
			var template = provider.get(name);
			result = template.execute(context, macrosFor(name));
		}catch (e:Dynamic) {
			trace("template execution error:");
			trace(e);
		}
		return result;
	}
	

	function macrosFor(name){
		return macros;
	}
	
	function renderObject(resolve:String->Dynamic, subtemplateName, obj:Displayable) {
		var subview:TemplateView = new TemplateView(provider, subtemplateName );
		if(Std.is(obj, Displayable)) subview.assignDisplayable(obj);
		return subview.renderTemplate();
	}
	
	function renderIterable(resolve:String->Dynamic, subtemplateName, obj:Iterable<Dynamic> ) {
		var subview:TemplateView = new TemplateView(provider, subtemplateName);
		subview.assign("parent", context);
		var buf = new StringBuf();
		for (i in obj) {
			if (Std.is(i, Displayable)) {
				subview.assignDisplayable(cast i);
				buf.add(subview.renderTemplate());
			}else {
				//not iterable: assign all fields
				var fields = Reflect.fields(i);
				for ( f in fields ) {
					var value = Reflect.field(i, f);
					subview.assign(f, value);
				}
				buf.add(subview.renderTemplate());
				//throw i + " should be Displayable";
			}
		}
		return buf.toString();
	}
	
	function dateFormat(resolve:String -> Dynamic, dateStr:Dynamic, format:String) {
		var date = Date.fromString(dateStr);
		if (Std.is(date, Date))
			return DateTools.format(date, format);
		else
			return "";
	}
	
	function noCache(resolve:String->Dynamic, uri:String) {
		return uri+"noCache=?"+(haxe.Timer.stamp() * 1000);
	}
	
	function setMacro(resolve:String->Dynamic, name:String, value:Dynamic) {
		this.assign(name, value);
		return "";
	}
	
	function ternarySetMacro(resolve:String->Dynamic, name:String, condition:Dynamic, ifTrue:Dynamic, ifFalse:Dynamic) {
		this.assign(name, (condition == false || condition == "true") ? ifTrue : ifFalse);
		return "";
	}
	
	function ternaryMacro(resolve:String->Dynamic, condition:Dynamic, ifTrue:Dynamic, ifFalse:Dynamic) {
		return (condition == false || condition == "true") ? ifTrue : ifFalse;
	}
	
	function markValidationError(resolve:String -> Dynamic, param:String) {
		if ( Reflect.hasField(context.validationErrors, param)) {
			var message = Reflect.field( context.validationErrors, param);
			return " style=\" border: 2px solid red;\"";
		}
		return "";
	}
	
	public function addMacro(macroName:String, f:Dynamic ) {
		Reflect.setField(macros, macroName, f);
	}
}